package com.haibara.codesubmiteval.judge.service.impl;

import cn.hutool.json.JSONUtil;
import com.haibara.codesubmiteval.constant.AuthConstant;
import com.haibara.codesubmiteval.constant.KafkaConstant;
import com.haibara.codesubmiteval.constant.RedisKeyConstant;
import com.haibara.codesubmiteval.judge.model.ExecuteCodeRequest;
import com.haibara.codesubmiteval.judge.model.ExecuteCodeRespond;
import com.haibara.codesubmiteval.judge.service.JudgeService;
import com.haibara.codesubmiteval.judge.strategy.DangerousCodeDetector;
import com.haibara.codesubmiteval.judge.strategy.JudgeContext;
import com.haibara.codesubmiteval.judge.strategy.JudgeManager;
import com.haibara.codesubmiteval.kafka.processor.Processor;
import com.haibara.codesubmiteval.mapper.QuestionMapper;
import com.haibara.codesubmiteval.model.entity.Question;
import com.haibara.codesubmiteval.model.entity.QuestionCase;
import com.haibara.codesubmiteval.model.entity.QuestionSubmit;
import com.haibara.codesubmiteval.model.entity.User;
import com.haibara.codesubmiteval.model.enums.JudgeResultEnum;
import com.haibara.codesubmiteval.model.enums.QuestionSubmitStatusEnum;
import com.haibara.codesubmiteval.service.QuestionCaseService;
import com.haibara.codesubmiteval.service.QuestionService;
import com.haibara.codesubmiteval.service.QuestionSubmitService;
import com.haibara.codesubmiteval.service.UserService;
import com.haibara.codesubmiteval.service.impl.Top10Service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

@Service
@Slf4j
public class JudgeServiceImpl implements JudgeService {

    @Resource
    private QuestionService questionService;
    @Resource
    private QuestionMapper questionMapper;
    @Resource
    private UserService userService;
    @Resource
    private QuestionSubmitService questionSubmitService;
    @Resource
    private QuestionCaseService questionCaseService;
    @Resource
    private JudgeManager judgeManager;
    @Resource
    private Processor processor;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private DangerousCodeDetector dangerousCodeDetector;
    @Resource
    private Top10Service top10Service;

    /**
     * 执行判题，如果发生异常则将用户题目提交记录判题结果设置为System Error
     */
    @Override
    public void doJudge(ExecuteCodeRespond executeCodeRespond) {
        Long questionSubmitId = executeCodeRespond.getQuestionSubmitId();
        QuestionSubmit questionSubmit = questionSubmitService.getById(questionSubmitId);
        Question question = questionService.getById(questionSubmit.getQuestionId());
        // 1.判断运行的结果
        // 若返回的ExecuteCodeRespond中状态为 Error，说明执行有异常，不需要进一步判断结果
        // 这里代表用户代码的执行结果应是 ：Runtime Error、Time Limit Exceeded、System Error
        if (executeCodeRespond.getResultStatus() != ExecuteCodeRespond.SUCCESS) {
            log.warn("运行失败 : {}", executeCodeRespond);
            // 将状态设置为失败
            questionSubmit.setStatus(QuestionSubmitStatusEnum.FAILED.getValue());
            // 设置错误信息
            if (executeCodeRespond.getResultStatus() == ExecuteCodeRespond.ERROR) {
                // Runtime Error or Time Limit Exceeded
                questionSubmit.setJudgeResult(executeCodeRespond.getMessage());
            } else {
                // System Error
                questionSubmit.setJudgeResult(JudgeResultEnum.SYSTEM_ERROR.getText());
            }
            // 将时间消耗和空间消耗设置为0
            questionSubmit.setTimeUsed(0);
            questionSubmit.setMemoryUsed(0);
            questionSubmitService.updateById(questionSubmit);
            // 删除Redis中 正在执行的题目提交信息id 对应字段
            stringRedisTemplate.opsForSet().remove(
                    RedisKeyConstant.executingCodeId,
                    questionSubmitId.toString()
            );
            return;
        }
        // 2.若返回的结果执行正常，则构建JudgeContext上下文，用于进一步判断结果是否正确
        List<QuestionCase> questionCases = questionCaseService.getQuestionCase(questionSubmit.getQuestionId());
        JudgeContext judgeContext = JudgeContext.builder()
                .questionCaseList(questionCases)
                .userId(questionSubmit.getUserId())
                .outputList(executeCodeRespond.getOutputList())
                .fileOutputList(executeCodeRespond.getFileOutputList())
                .timeUsed(executeCodeRespond.getTimeUsed())
                .memoryUsed(executeCodeRespond.getMemoryUsed())
                .timeLimit(question.getTimeLimit())
                .memoryLimit(question.getMemoryLimit())
                .build();

        // 3.执行判断
        JudgeResultEnum judgeResult = judgeManager.doJudge(judgeContext);

        // 4.根据判题结果修改数据库中的判题结果
        questionSubmit.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        questionSubmit.setTimeUsed(executeCodeRespond.getTimeUsed());
        questionSubmit.setMemoryUsed(executeCodeRespond.getMemoryUsed());
        questionSubmit.setJudgeResult(judgeResult.getText());
        questionSubmitService.updateById(questionSubmit);
        // 5. 修改用户通过题目id集合与题目通过数
        if (JudgeResultEnum.ACCEPTED.getText().equals(judgeResult.getText())) {
            Long userId = questionSubmit.getUserId();
            User user = userService.getById(userId);
            Set<Long> acceptedQuestionIdsSet = new HashSet<>(JSONUtil.toList(user.getAcceptedQuestionIds(), Long.class));
            // 新题目通过
            if (!acceptedQuestionIdsSet.contains(questionSubmit.getQuestionId())) {
                acceptedQuestionIdsSet.add(questionSubmit.getQuestionId());
                user.setAcceptedQuestionIds(JSONUtil.toJsonStr(acceptedQuestionIdsSet));
                // 增加分数
                user.setScore(user.getScore() + question.getScore());
                userService.updateById(user);
                // 更新Redis - 用户通过题目id集合
                stringRedisTemplate.opsForSet().add(
                        RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, userId.toString()),
                        question.getId().toString()
                );
                // 更新Redis - top榜单
                if (user.getUserRole().equals(AuthConstant.USER)) {
                    top10Service.incrUserScore(userId, question.getScore());
                }
            }
            // 题目通过数
            questionMapper.increaseQuestionAcceptedNum(question.getId());
            // 更新用户缓存
            stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userKey, userId.toString()));
        }
        stringRedisTemplate.opsForSet().remove(
                RedisKeyConstant.executingCodeId,
                questionSubmitId.toString()
        );
    }

    @Override
    public void submitToCodeSandBox(Long questionSubmitId) {
        // 1.传入题目提交id，获取到对应题目、提交信息
        QuestionSubmit questionSubmit = questionSubmitService.getById(questionSubmitId);
        Long questionId = questionSubmit.getQuestionId();
        // 2.若题目状态不为等待中，就不重复执行 （幂等性）
        if (!Objects.equals(questionSubmit.getStatus(), QuestionSubmitStatusEnum.WAITING.getValue())) {
            return;
        }
        // 3.修改判题状态为判题中，防止重复执行
        questionSubmit.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        questionSubmitService.updateById(questionSubmit);
        // 4.判断是否有危险代码
        List<String> dangerousCodes = dangerousCodeDetector.detectDangerousCodes(questionSubmit.getCode());
        if (!dangerousCodes.isEmpty()) {
            // 有危险代码
            log.error("检测到危险代码 questionSubmitId: {}, 危险代码: {}", questionSubmitId, dangerousCodes);
            questionSubmit.setJudgeResult(JudgeResultEnum.DANGEROUS_OPERATION.getText());
            questionSubmit.setStatus(QuestionSubmitStatusEnum.FAILED.getValue());
            questionSubmit.setTimeUsed(0);
            questionSubmit.setMemoryUsed(0);
            questionSubmitService.updateById(questionSubmit);
            stringRedisTemplate.opsForSet().remove(
                    RedisKeyConstant.executingCodeId,
                    questionSubmitId.toString()
            );
            return;
        }
        // 5.获取判题用例并构建请求
        List<QuestionCase> questionCases = questionCaseService.getQuestionCase(questionSubmit.getQuestionId());
        Question question = questionMapper.selectById(questionId);
        String code = questionSubmit.getCode();
        ExecuteCodeRequest ecr = ExecuteCodeRequest.builder()
                .questionSubmitId(questionSubmitId)
                .questionCases(questionCases)
                .code(code)
                .questionId(questionId)
                .timeLimit(question.getTimeLimit())
                .userId(questionSubmit.getUserId())
                .build();
        // 6.发送Kafka信息
        processor.sendMsg(KafkaConstant.RUN_CODE_TOPIC, JSONUtil.toJsonStr(ecr));
    }
}
